package com.onpositive.wikistorage;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import storage.v1.ProperyDataExtractor;

import com.onpositive.data.units.UnitKind;
import com.onpositive.mediawiki.propertyparser.ParsedCategory;
import com.onpositive.mediawiki.propertyparser.ParsedDocument;
import com.onpositive.mediawiki.propertyparser.PropertyCharacteristic;
import com.onpositive.mediawiki.propertyparser.PropertyGroup;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedProperty;
import com.onpositive.units.ParsedValue;
import com.onpositive.units.XMLValueParser;

public class XMLInterface {
	
	private static final String NODE_NAME_PROPERTY_STATS = "property-stats";
	private static final String NODE_NAME_WIKI_REFS_CAPTIONS = "wiki-refs-captions";
	private static final String NODE_NAME_WIKI_REFS = "wiki-refs";
	public static final String NODE_NAME_ROOT = "wikidata" ;
	public static final String NODE_NAME_DOCUMENTS_LIST = "file-list" ;
	public static final String NODE_NAME_LINKS_LIST = "links-list" ;
	public static final String NODE_NAME_LINK = "link" ;
	public static final String NODE_NAME_LINK_CAPTION = "link-caption" ;
	public static final String NODE_NAME_PARSED_DOCUMENT = "file" ;
	public static final String NODE_NAME_PROPERTIES_CHARACTERISTICS_LIST = "property-characteristics-list" ;
	//public static final String NODE_NAME_WIKI_REFS_STRRING_POOL = "wiki-refs-string-pool" ;
	public static final String NODE_NAME_PROPERTY_GROUPS_LIST = "property-groups-list" ;
	public static final String NODE_NAME_PROPERTY_GROUP = "property-group" ;
	public static final String NODE_NAME_PROPERTY_GROUP_PARENT = "parent-group" ;
	public static final String NODE_NAME_PROPERTY_CHARACTERISTIC = "characteristic" ;
	
	public static int FLAG_LEAVE_PROPERTY_CHARACTERISTICS = 1 ;
	public static int FLAG_LEAVE_PROPERTY_GROUPS = 2 ;
	public static int INHERIT_USED_PROPERTY_CHARACTERISTICS = 4;
	
	
	
	public static Element produceElement( ParsedDocument parsedDocument,
										  Document document,
										  ParsedCategory oldCategory )
	{
		Element result = document.createElement( "file" ) ;
		String name = parsedDocument.getName().trim();
		result.setAttribute( "filename", name ) ;
		result.setAttribute( "parent-categories", parsedDocument.getParentCategoriesString() ) ;
		
		
		ParsedDocument bufferDocument = parsedDocument ; 
		if( oldCategory != null ){
			ParsedDocument doc = oldCategory.getParsedDocument( name ) ;
			if( doc != null )
				bufferDocument = doc ;
		}
		result.setAttribute( "default-picture", bufferDocument.getDefaultPicture() ) ;		

		Collection<ParsedProperty> properties = parsedDocument.getProps().values();
		for (ParsedProperty property:properties)
			property.write( result );
		
		return result ;
	}
	
	public static ParsedDocument produceParsedDocument( Element fileElement )
	{
		ParsedDocument result = new ParsedDocument() ;
		result.setName( fileElement.getAttribute( "filename" ) ) ;
		//result.setDefaultPicture( fileElement.getAttribute("default-picture")) ;
		result.setParentCategoriesString( fileElement.getAttribute("parent-categories")) ;
		
		NodeList children = fileElement.getChildNodes() ;
		int l = children.getLength() ;
		for( int i = 0 ; i < l ; i++  )
		{
			Node n = children.item(i) ;
			if( n instanceof Element )
			{
				Element valueElement = (Element)n ;
				String propertyName = valueElement.getAttribute("name");
				
				
				ParsedProperty property = result.getPropertyForContribution( propertyName ) ;
//				property.setStringPoolProvider(category) ;
				ParsedValue value =  XMLValueParser.produceParsedValue( valueElement ) ;				
				property.addValue(value) ;				
			}			
		}

		return result ;
	}
	
	public static PropertyCharacteristic producePropertyCharacteristic( Element element )
	{
		PropertyCharacteristic result = new PropertyCharacteristic() ;
		NamedNodeMap attributes = element.getAttributes() ;
		int l = attributes.getLength() ;
		for( int i = 0 ; i < l ; i++ )
		{
			Node n = attributes.item(i) ;
			if( n instanceof Attr )
			{
				Attr a = (Attr)n ;
				String attributeName  = a.getName() ;
				String attributeValue = a.getNodeValue() ;
				result.setCharacteristic(attributeName, attributeValue) ;
			}
		}
		return result ;
	}
	
	public static PropertyGroup producePropertyGroup( Element element )
	{
		String name = element.getAttribute("name") ;
		ArrayList<String> parentNames = new ArrayList<String>() ;	
		
		NodeList children = element.getChildNodes() ;
		int l = children.getLength() ;
		for( int i = 0 ; i < l ; i++  )
		{
			Node n = children.item(i) ;
			if( n instanceof Element )
			{
				Element parentGroupElement = (Element)n ;
				String pName = parentGroupElement.getAttribute("name") ;
				parentNames.add( pName ) ;				
			}			
		}		
		return new PropertyGroup( name, parentNames ) ;
	}
	
	public static ParsedCategory loadParsedCategory( String filePath )
	{
		
		DocumentBuilder builder ;		
		try {
			builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() ;
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return null;
		}
		
		Document document ;
		
		try {
			document = builder.parse( new FileInputStream(new File( filePath ) ) ) ;
		} catch (FileNotFoundException e) {
			//e.printStackTrace();
			return null;
		} catch (SAXException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}

		Element root = document.getDocumentElement() ;
		ParsedCategory category = new ParsedCategory(new File(filePath).getParent()) ;
		NodeList rootList = root.getChildNodes() ;
		
		int l0 = rootList.getLength() ;
		for( int i = 0 ; i < l0 ; i++ )
		{
			Node n0 = rootList.item(i);
			if( n0 instanceof Element )
			{
				String elementName = n0.getNodeName() ;
				Element levelOneElement = (Element)n0 ;
				if( elementName.equals(NODE_NAME_DOCUMENTS_LIST) ){				
					
					NodeList fileElementList = levelOneElement.getChildNodes() ;
					int l1 = fileElementList.getLength() ;
					for( int j = 0 ; j < l1 ; j++ )
					{
						Node n1 = fileElementList.item(j);
						if( n1 instanceof Element )
						{
							Element fileElement = (Element)n1 ;	
							ParsedDocument parsedDocument = XMLInterface.produceParsedDocument( fileElement ) ;
							category.addParsedDocument( parsedDocument ) ;
						}
					}
				}
				else if( elementName.equals(NODE_NAME_PROPERTIES_CHARACTERISTICS_LIST) ){				
					
					NodeList propertyCharacteristicsList = levelOneElement.getChildNodes() ;
					int l1 = propertyCharacteristicsList.getLength() ;
					for( int j = 0 ; j < l1 ; j++ )
					{
						Node n1 = propertyCharacteristicsList.item(j);
						if( n1 instanceof Element )
						{
							Element propertyCharacteristicElement = (Element)n1 ;	
							PropertyCharacteristic pc = producePropertyCharacteristic( propertyCharacteristicElement ) ;
							
							String samplesCountString = pc.getCharacteristic(PropertyCharacteristic.CHAR_SAMPLES_COUNT_LABEL);
							if( samplesCountString.length() == 0 )
								samplesCountString = "0" ;
							
							Integer count = Integer.parseInt( samplesCountString );
							if( count < 20 )
								pc.setDepricated( true ) ;
								
							category.addPropertyCharacteristic(pc) ;
						}
					}
				}
				else if( elementName.equals( NODE_NAME_PROPERTY_GROUPS_LIST ) ){
					
					NodeList propertyCharacteristicsList = levelOneElement.getChildNodes() ;
					int l1 = propertyCharacteristicsList.getLength() ;
					for( int j = 0 ; j < l1 ; j++ )
					{
						Node n1 = propertyCharacteristicsList.item(j);
						if( n1 instanceof Element )
						{
							Element propertyGroupElement = (Element)n1 ;	
							PropertyGroup gr = producePropertyGroup( propertyGroupElement ) ;
							category.addPropertyGroup( gr ) ;
						}
					}					
				}
				else if( elementName.equals( NODE_NAME_WIKI_REFS ) )
				{
					category.initRefs( levelOneElement ) ;				
				}
				else if( elementName.equals( NODE_NAME_WIKI_REFS_CAPTIONS ) )
				{
					category.initCaptions( levelOneElement ) ;				
				}
			}			
		}
//		ParsedProperty prop = category.getParsedDocument("Mikoyan-Gurevich MiG-21").getProperty("picture-comments") ;
		return category ;
	}
	
	public static void saveParsedCategory( String filePath, ParsedCategory category, int flags )
	{
		ParsedCategory oldCategory = null;
		if( flags != 0 )
			oldCategory = loadParsedCategory(filePath) ;
		if (oldCategory==null){
			oldCategory=new ParsedCategory(filePath);
		}

		DocumentBuilder builder ;		
		try {
			builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() ;
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return;
		}
		
		Document newDocument = builder.newDocument() ;
		Element rootElement = newDocument.createElement( NODE_NAME_ROOT );
		newDocument.appendChild( rootElement ) ;
		
		Element fileListElement = newDocument.createElement( NODE_NAME_DOCUMENTS_LIST );
		rootElement.appendChild( fileListElement ) ;
		for( ParsedDocument parsedDocument : category.getDocumentsList() )
		{
			Element fileElement = XMLInterface.produceElement( parsedDocument, newDocument, oldCategory ) ;
			fileListElement.appendChild(fileElement) ;
		}
		

		Element propertyCharacteristicsListElement = ( (flags & FLAG_LEAVE_PROPERTY_CHARACTERISTICS) == 0)
												   ?    propuceProperyCharacteristicsElement( category, newDocument)
												   :    propuceProperyCharacteristicsElement( category.getChracteristicsMap().isEmpty()?category: oldCategory, newDocument) ;
												   
		if( ( flags & INHERIT_USED_PROPERTY_CHARACTERISTICS ) != 0 ||category.getChracteristicsMap().isEmpty())
		{
			for( String n : category.getPropertyNames() ) {
				PropertyCharacteristic characteristics = oldCategory.getCharacteristics(n);
				if(characteristics!=null){
				category.addPropertyCharacteristic( characteristics) ;
				}
			}			
		}
		rootElement.appendChild( propertyCharacteristicsListElement ) ;
  
		
		
		Element propertyGroupsListElement = ( (flags & FLAG_LEAVE_PROPERTY_GROUPS ) == 0)
										  ?    produceGroupsListElement( category, newDocument)
										  :    produceGroupsListElement( oldCategory, newDocument) ;
										  
		rootElement.appendChild( propertyGroupsListElement ) ;
		
		Element refsElement = rootElement.getOwnerDocument().createElement(NODE_NAME_WIKI_REFS) ;
		rootElement.appendChild( refsElement ) ;
		LinkStorage refs = category.getRefs();
		if( refs != null )
			refs.writeValues(refsElement) ;
		
		Element captionsElement = rootElement.getOwnerDocument().createElement(NODE_NAME_WIKI_REFS_CAPTIONS) ;
		rootElement.appendChild( captionsElement ) ;
		LinkStorage captions = category.getCaptions();
		if( captions != null )
			captions.writeValues(captionsElement) ;		
		
		Transformer transformer ;
		try {
			transformer =  TransformerFactory.newInstance().newTransformer() ;
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
			return ;
		} catch (TransformerFactoryConfigurationError e){
			e.printStackTrace();
			return ;
		}
		transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
		
		Element root = newDocument.getDocumentElement() ;
		File file = new File( filePath );
		file.getParentFile().mkdirs();
		StreamResult sr = new StreamResult( file) ;
		try {
			transformer.transform( new DOMSource(root),  sr ) ;
		} catch (TransformerException e) {
			e.printStackTrace();
			return ;
		}		
	}

	

	private static Element produceGroupsListElement( ParsedCategory category, Document newDocument)
	{
		Element propertyGroupsListElement = newDocument.createElement( NODE_NAME_PROPERTY_GROUPS_LIST );
		for( PropertyGroup gr : category.getPropertyGroups().values() )
		{
			Element propertyGroupElement = newDocument.createElement( NODE_NAME_PROPERTY_GROUP ) ;
			propertyGroupsListElement.appendChild(propertyGroupElement) ;
			propertyGroupElement.setAttribute( "name"  , gr.getName()   ) ;
			String[] pStr = gr.getParentStrings() ;
			if( pStr == null )
				continue ;
			for( String s : pStr )
			{
				Element parentGroupElement = newDocument.createElement( NODE_NAME_PROPERTY_GROUP_PARENT ) ;
				propertyGroupElement.appendChild( parentGroupElement ) ;
				parentGroupElement.setAttribute( "name"  , s ) ;
			}
		}
		return propertyGroupsListElement;
	}

	private static Element propuceProperyCharacteristicsElement(
			ParsedCategory category, Document newDocument) {
		Element propertyCharacteristicsListElement = newDocument.createElement( NODE_NAME_PROPERTIES_CHARACTERISTICS_LIST );
		
		//final String[] labels = PropertyCharacteristic.CHARACTERISTICS_LABELS_ARRAY ;
		for( PropertyCharacteristic pc : category.getChracteristicsMap().values() )
		{
			Element characteristicElement = newDocument.createElement( NODE_NAME_PROPERTY_CHARACTERISTIC ) ;
			propertyCharacteristicsListElement.appendChild(characteristicElement) ;
			for( String l : pc.getCharacteristicsList() )
			{
				String value = pc.getCharacteristic( l );
				characteristicElement.setAttribute( l, value) ;
			}
		}
		return propertyCharacteristicsListElement;
	}
	
	public static void savePropertyStatistics(ProperyDataExtractor extractor, String filePath){
		
		DocumentBuilder builder ;		
		try {
			builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() ;
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return;
		}
		
		Document newDocument = builder.newDocument() ;
		Element rootElement = newDocument.createElement( NODE_NAME_PROPERTY_STATS );
		newDocument.appendChild( rootElement ) ;
		
		HashMap<String, HashMap<String, Integer>> parserStatsMap = extractor.getParserStatsMap();
		HashMap<String, HashMap<UnitKind, Integer>> unitStatsMap = extractor.getUnitStatsMap();
		HashSet<String> serviceProperties = extractor.getServiceProperties();
		
		Element allPropsElement = newDocument.createElement("all-properties");
		rootElement.appendChild( allPropsElement);
		for(Map.Entry<String, HashMap<String, Integer>> entry: parserStatsMap.entrySet()){
			
			String propName = entry.getKey();
			if(propName==null)
				continue;
			
			Element propElement = newDocument.createElement("property");
			allPropsElement.appendChild(propElement);
			
			propElement.setAttribute("name", propName);
			
			HashMap<String, Integer> parserMap = entry.getValue();
			if(parserMap==null||parserMap.isEmpty())
				continue;
			
			HashMap<UnitKind, Integer> unitsMap = unitStatsMap.get(propName);
			appendParserStats(propElement,parserMap,unitsMap);			
		}		
		Transformer transformer ;
		try {
			transformer =  TransformerFactory.newInstance().newTransformer() ;
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
			return ;
		} catch (TransformerFactoryConfigurationError e){
			e.printStackTrace();
			return ;
		}
		transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
		Element root = newDocument.getDocumentElement() ;
		File file = new File( filePath );
		file.getParentFile().mkdirs();
		StreamResult sr = new StreamResult( file) ;
		try {
			transformer.transform( new DOMSource(root),  sr ) ;
		} catch (TransformerException e) {
			e.printStackTrace();
			return ;
		}		
	}

	private static void appendParserStats(
			Element propElement,
			HashMap<String, Integer> parserMap,
			HashMap<UnitKind, Integer> unitsMap)
	{
		for(Map.Entry<String, Integer> entry : parserMap.entrySet()){
			String id = entry.getKey();
			if(id==null)
				continue;
			
			Integer count = entry.getValue();
			if(count==null)
				count=-1;
			
			Element element = propElement.getOwnerDocument().createElement("parser");
			propElement.appendChild(element);
			element.setAttribute("id", id);
			element.setAttribute("count", ""+count);
			
			if(id.equals(ParsedDimension.ID)){
				appendInitStats(element,unitsMap);
			}
		}
		
	}

	private static void appendInitStats(Element element,HashMap<UnitKind, Integer> unitsMap)
	{
		for(Map.Entry<UnitKind, Integer> entry : unitsMap.entrySet()){
			UnitKind uk = entry.getKey();
			if(uk==null)
				continue;
			
			Integer count = entry.getValue();
			if(count==null)
				count=-1;
			
			Element unitElement = element.getOwnerDocument().createElement("unit-kind");
			element.appendChild(unitElement);
			unitElement.setAttribute("kind", uk.name());
			unitElement.setAttribute("count", ""+count);
		}		
	}
	
}
